package com.jingdun.sport.data.environment

import com.baomidou.mybatisplus.core.metadata.IPage
import com.baomidou.mybatisplus.extension.plugins.pagination.Page
import com.losttemple.sql.language.dialects.*
import com.losttemple.sql.language.operator.*
import com.losttemple.sql.language.types.SqlType
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.stereotype.Service
import java.sql.Connection
import javax.sql.DataSource

@Service
class DataConnectionCreator(@Autowired private val dataSource: DataSource): ConnectionFactory {
    override fun <T> connect(handler: (Connection) -> T): T {
        return dataSource.connection.use { connection ->
            handler(connection)
        }
    }

    override fun close() {
    }
}

@Service
class DataEnvironment(creator: DataConnectionCreator):
        GenericDialectEnvironment(creator, {MySqlDialect()}) {
    fun <T, R> UsedPagedDbSet<T>.select(mapper: QueryResultAccessor.(T) -> R): IPage<R> {
        return this.select(mapper, this@DataEnvironment)
    }
}

fun InsertRetEnvironment.intGeneratedKey(): Int? {
    return getIntByName("GENERATED_KEY")
}

data class JoinTables<LEFT, RIGHT> (
    val left: LEFT,
    val right: RIGHT
)

class SelectedJoin<LEFT, RIGHT, R>(
        private val source: JoinSource<LEFT, RIGHT>,
        private val selector: JoinTables<LEFT, RIGHT>.() -> R
) {
    fun on(predicate: R.() -> SqlType<Boolean>): DbSet<R> {
        return source.on { left, right ->
            val tables = JoinTables(left, right)
            val v = tables.selector()
            v.predicate()
        }.select { left, right ->
            val tables = JoinTables(left, right)
            tables.selector()
        }
    }
}

fun <LEFT, RIGHT, R> JoinSource<LEFT, RIGHT>.select(selector: JoinTables<LEFT, RIGHT>.() -> R)
        : SelectedJoin<LEFT, RIGHT, R> {
    return SelectedJoin(this, selector)
}

class PagedDbSet<T>(
        private val source: DbSet<T>,
        private val page: Page<*>
) {
    fun with(config: (CalcConfig, T) -> Unit): UsedPagedDbSet<T> {
        return UsedPagedDbSet<T>(source, page) {
            with(config)
        }
    }

    fun use(config: CalcConfig.(T) -> Unit): UsedPagedDbSet<T> {
        return UsedPagedDbSet<T>(source, page) {
            use(config)
        }
    }
}

class UsedPagedDbSet<T>(
        private val source: DbSet<T>,
        private val page: Page<*>,
        private val useConfig: DbSet<T>.() -> DbResult<T>
) {
    fun <R> select(mapper: QueryResultAccessor.(T) -> R, environment: DialectEnvironment): IPage<R> {
        val size = page.size
        val offset = size * (page.current - 1)
        val total = environment.run {
            source.aggregate { count() }()
        }
        val pagedSource = source.limitWithOffset(size.toInt(), offset.toInt())
        val resultSet = environment.run {
            pagedSource.useConfig().select(mapper)
        }
        val pagedResult = Page<R>(page.current, page.size, total!!.toLong())
        pagedResult.records = resultSet
        return pagedResult
    }
}

fun <T> DbSet<T>.page(page: Page<*>): PagedDbSet<T> {
    return PagedDbSet(this, page)
}